#region File Header
//----------------------------------------------------------------
// Copyright (C) 2002 Stefan Kyntchev
// All rights reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY 
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT 
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR 
// FITNESS FOR A PARTICULAR PURPOSE.
//----------------------------------------------------------------
#endregion File Header

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Data;
using System.Collections;
using System.IO;
using System.Globalization;

namespace AirWeb.ApplicationTools.Image
{
	/// <summary>
	/// Defines the various EXIFF proeprties of the image
	/// </summary>
	public class ImageProperty
	{
		PropertyItem m_item;
		static Hashtable m_prop; 
		bool m_knownProp = true;

		/// <summary>
		/// Defines a named values of known properties
		/// </summary>
		public enum ImagePropsIds
		{
			IMAGE_WIDTH =		283,
			IMAGE_LENGTH =		284,
			EQUPMENT_MAKE =		271,
			CAMERA_MODEL =		272,
			CREATION_SOFTWARE = 305,
			DATE_PIC_TAKEN =	306,
			DATE_ORIGINAL =		36867,
			DATE_DIGITIZED =	36868,
			TITLE =				40091,
			SUBJECT =			40095,
			KEYWORDS =			40094,
			COMMENTS =			40092,
			AUTHOR =			40093
		}


		/// <summary>
		/// Static constructor. Initializes a hashtable with IDs and descriptions of the properties
		/// </summary>
		static ImageProperty()
		{
			m_prop = new Hashtable();
			m_prop.Add((int)ImagePropsIds.EQUPMENT_MAKE,"Equipment make");
			m_prop.Add((int)ImagePropsIds.CAMERA_MODEL,"Camera Model");
			m_prop.Add((int)ImagePropsIds.CREATION_SOFTWARE,"Creation Software");
			m_prop.Add((int)ImagePropsIds.DATE_PIC_TAKEN,"Date Picture Taken");
			m_prop.Add((int)ImagePropsIds.DATE_ORIGINAL,"Date Original");
			m_prop.Add((int)ImagePropsIds.DATE_DIGITIZED,"Date Digitized");

			m_prop.Add((int)ImagePropsIds.TITLE,"Title");
			m_prop.Add((int)ImagePropsIds.SUBJECT,"Subject");
			m_prop.Add((int)ImagePropsIds.KEYWORDS,"Keywords");
			m_prop.Add((int)ImagePropsIds.COMMENTS,"Comments");
			m_prop.Add((int)ImagePropsIds.AUTHOR,"Author");

			m_prop.Add(283,"Vertical Resolution");
			m_prop.Add(282,"Horizontal Resolution");
			m_prop.Add(274,"Orientation of Image");
			m_prop.Add(296,"Unit of X and Y resolution");

			m_prop.Add(33434,"Exposure time");					
			m_prop.Add(33437,"F number");							
			m_prop.Add(34850,"Exposure program");
			m_prop.Add(34852,"Spectral sensitivity");				
			m_prop.Add(34855,"ISO speed rating");					
			m_prop.Add(34856,"Optoelectric conversion factor");	
			m_prop.Add(37377,"Shutter speed");					
			m_prop.Add(37378,"Aperture");							
			m_prop.Add(37379,"Brightness");						
			m_prop.Add(37380,"Exposure bias");					
			m_prop.Add(37381,"Maximum lens aperture");			
			m_prop.Add(37382,"Subject distance");					
			m_prop.Add(37383,"Metering mode");					
			m_prop.Add(37384,"Light source");						
			m_prop.Add(37385,"Flash");							
			m_prop.Add(37386,"Lens focal length");				
			m_prop.Add(41483,"Flash energy");						
			m_prop.Add(41484,"Spatial frequency response");		
			m_prop.Add(41486,"Focal plane X resolution");			
			m_prop.Add(41487,"Focal plane Y resolution");			
			m_prop.Add(41488,"Focal plane resolution unit");		
			m_prop.Add(41492,"Subject location");					
			m_prop.Add(41493,"Exposure index");					
			m_prop.Add(41495,"Sensing method");					
			m_prop.Add(41728,"File source");						
			m_prop.Add(41729,"Scene type");						
			m_prop.Add(41730,"CFA pattern");						

			m_prop.Add(36864,"Exif version");								
			m_prop.Add(40960,"Supported FlashPix version");					
			m_prop.Add(40961,"Color space information");					
			m_prop.Add(37121,"Meaning of each component");					
			m_prop.Add(37122,"Image compression mode");						
			m_prop.Add(40962,"Valid image width Pi");						
			m_prop.Add(40963,"Valid image height Pi");						
			m_prop.Add(37500,"Manufacturer notes");							
			m_prop.Add(37510,"User comments");								
			m_prop.Add(40964,"Related audio file");							
			m_prop.Add(37520,"DateTime subs econds");						
			m_prop.Add(37521,"DateTimeOriginal subseconds");				
			m_prop.Add(37522,"DateTimeDigitized subseconds");	
		}

		/// <summary>
		/// Creates an new property of specific type
		/// </summary>
		/// <param name="i_item"></param>
		public ImageProperty(PropertyItem i_item)
		{
			m_item = i_item;
		}
	
		/// <summary>
		/// This is the name of the property
		/// </summary>
		public string Name
		{
			get
			{
				string name;
				if(m_knownProp)
				{
					int p = m_item.Id;
					Object o = m_prop[m_item.Id];
					name =  (string)o;
				}
				else
				{
					name = m_item.Id.ToString();
				}

				return name;
			}
		}

		/// <summary>
		/// The string repesentation of the value
		/// </summary>
		public string Value
		{
			get
			{
				if(KnownProperty)
					return StrFromByteArray(m_item.Value);
				else	
					return IntFromByteArray(m_item.Value);
			}
			set
			{
				byte[] b = StrToByteArray(value);
				m_item.Value = b;
				m_item.Len = b.Length;
			}
		}

		/// <summary>
		/// The type of the property. Types 1 and 2 appear to be strings
		/// </summary>
		public string Type
		{
			get
			{
				return m_item.Type.ToString();
			}
		}

		/// <summary>
		/// True if the property is a recognized property
		/// </summary>
		public bool KnownProperty
		{
			get
			{
				return m_knownProp;
			}
		}

		/// <summary>
		/// This is the RAW proprty as returned by windows
		/// </summary>
		public PropertyItem PropItem
		{
			get
			{
				return m_item;
			}
		}

		/// <summary>
		/// Converts a nul terminated byte array to string
		/// </summary>
		/// <param name="i_b">byte array</param>
		/// <returns></returns>
		private string StrFromByteArray(byte[] i_b)
		{
			string s = "";
			for(int t = 0; t != i_b.Length; t++)
			{
				if(i_b[t] != 0)
					s += (char)i_b[t];				
			}

			return s;
		}

		/// <summary>
		/// Converts a string to a byte array
		/// </summary>
		/// <param name="s"></param>
		/// <returns></returns>
		private byte[] StrToByteArray(string s)
		{
			byte[] p = new byte[(s.Length+1)*2];

			int b = 0;
			for(int t = 0; t != s.Length; t++)
			{
				if(t < s.Length )
				{
					p[b] = (byte)s[t];
					p[b+1] = 0;
					b+=2;
				}
				else
					p[t] = 0;
			}

			return p;
		}

		/// <summary>
		/// Converts the first 4 bytes from the byte array to Hex string
		/// </summary>
		/// <param name="i_b"></param>
		/// <returns></returns>
		private string IntFromByteArray(byte[] i_b)
		{
			int i1 = 0, i2 = 0, i3 = 0, i4 = 0;

			i1 = i_b[0];

			if(i_b.Length > 1)
				i2 = i_b[1];

			if(i_b.Length > 2)
				i3 = i_b[2];

			if(i_b.Length > 3)
				i4 = i_b[3];

			return "Size: " + i_b.Length + " Value: " + i1.ToString("X") + "," +  i2.ToString("X") + "," + i3.ToString("X") + "," + i4.ToString("X");
		}

		/// <summary>
		/// Converts the value of the property to DateTime if possible
		/// </summary>
		/// <returns>DateTime value or throws a format exception</returns>
		public DateTime ToDateTime()
		{
			DateTime dt  = System.DateTime.ParseExact(Value,
				"yyyy:MM:dd HH:mm:ss",
				System.Globalization.DateTimeFormatInfo.CurrentInfo,
				System.Globalization.
				DateTimeStyles.AllowWhiteSpaces);

			return dt;
		}
	}

	/// <summary>
	/// Reads all properties from a given picture file. 
	/// </summary>
	public class PictureProperties : IDisposable
	{
		private Bitmap m_Bitmap;
		private string m_FileName = null;
		private string m_tempName = null;

		// Track whether Dispose has been called.
		private bool m_disposed = false;

		// Implement Idisposable.
		// Do not make this method virtual.
		// A derived class should not be able to override this method.
		public void Dispose()
		{
			Dispose(true);
			// Take yourself off of the Finalization queue 
			// to prevent finalization code for this object
			// from executing a second time.
			GC.SuppressFinalize(this);
		}

		// Dispose(bool disposing) executes in two distinct scenarios.
		// If disposing equals true, the method has been called directly
		// or indirectly by a user's code. Managed and unmanaged resources
		// can be m_disposed.
		// If disposing equals false, the method has been called by the 
		// runtime from inside the finalizer and you should not reference 
		// other objects. Only unmanaged resources can be m_disposed.
		protected virtual void Dispose(bool disposing)
		{
			// Check to see if Dispose has already been called.
			if(!this.m_disposed)
			{
				// If disposing equals true, dispose all managed 
				// and unmanaged resources.
				if(disposing)
				{
					// Dispose managed resources.
					if(m_Bitmap != null)
						m_Bitmap.Dispose();

					if(m_tempName != null)
						File.Delete(m_tempName);
				}
				// Release unmanaged resources. If disposing is false, 
				// only the following code is executed.
				
				//Some unmanaged resources

				// Note that this is not thread safe.
				// Another thread could start disposing the object
				// after the managed resources are m_disposed,
				// but before the m_disposed flag is set to true.
			}
			m_disposed = true;         
		}

		/// <summary>
		/// Constructor. Creates an instance based on a file
		/// </summary>
		/// <param name="i_fileName">Path to the image file</param>
		public PictureProperties(String i_fileName)
		{
			m_FileName = i_fileName;

			try
			{
				m_Bitmap = new Bitmap(m_FileName);
			}
			catch
			{
				throw new ArgumentException("Unable to open the specified file",i_fileName);
			}
		}

		/// <summary>
		/// Destructor
		/// </summary>
		~ PictureProperties()
		{

			// Do not re-create Dispose clean-up code here.
			// Calling Dispose(false)is optimal in terms of
			// readability and maintainability.
			Dispose(false);
		}

		/// <summary>
		/// Saves the properties
		/// </summary>
		public void Save()
		{
			if(this.m_disposed)
			{
				throw new ObjectDisposedException("This instance of PictureProperties class was already disposed!");
			}

			if(m_tempName == null)
				throw new ArgumentException("This instance is in Read Only mode!");

			m_Bitmap.Save(m_FileName);
		}

		/// <summary>
		/// Saves the properties to a new file
		/// </summary>
		/// <param name="i_filename">file to save the properties to</param>
		public void Save(string i_filename)
		{
			if(this.m_disposed)
			{
				throw new ObjectDisposedException("This instance of PictureProperties class was already disposed!");
			}

			m_Bitmap.Save(i_filename);
		}

		/// <summary>
		/// Returns a property based on a property ID
		/// </summary>
		/// <param name="i_propId"></param>
		/// <returns></returns>
		public ImageProperty GetProperty(ImageProperty.ImagePropsIds i_propId)
		{
			if(this.m_disposed)
			{
				throw new ObjectDisposedException("This instance of PictureProperties class was already disposed!");
			}

			PropertyItem pi = m_Bitmap.GetPropertyItem((int)i_propId);
			if(pi != null)
			{
				ImageProperty p = new ImageProperty(pi);
				return p;
			}

			throw new ArgumentException("Unable to find the requested property",i_propId.ToString());
		}

		/// <summary>
		/// Sets a property based on a property ID
		/// </summary>
		/// <param name="i_propId"></param>
		/// <returns></returns>
		public void SetProperty(ImageProperty i_prop)
		{
			if(this.m_disposed)
			{
				throw new ObjectDisposedException("This instance of PictureProperties class was already disposed!");
			}

			m_Bitmap.SetPropertyItem(i_prop.PropItem);
		}

		/// <summary>
		/// Sets a keyword property to  a list of string values
		/// </summary>
		/// <param name="i_keywords">ArrayList of string values</param>
		public void ApplyKeywords(ArrayList i_keywords)
		{
			if(this.m_disposed)
			{
				throw new ObjectDisposedException("This instance of PictureProperties class was already disposed!");
			}

			ImageProperty p = GetProperty(ImageProperty.ImagePropsIds.KEYWORDS);
			
			string val = p.Value;

			foreach(string s in i_keywords)
			{
				if(val.IndexOf(s) >= 0)
					continue;  // the keyword is already set
				else
				{
					if(val.Length > 0) // is not empty
						val += ", ";

					val += s;  // add the keyword
				}
			}
			p.Value = val;  // save it
			SetProperty(p);

			ImageProperty p1 = GetProperty(ImageProperty.ImagePropsIds.KEYWORDS);
			string val1 = p1.Value;
		}

		/// <summary>
		/// Remuves the specified keywords from the keyword property
		/// </summary>
		/// <param name="i_keywords">ArrayList of keywords to remove</param>
		public void ClearKeywords(ArrayList i_keywords)
		{
			if(this.m_disposed)
			{
				throw new ObjectDisposedException("This instance of PictureProperties class was already disposed!");
			}

			ImageProperty p = GetProperty(ImageProperty.ImagePropsIds.KEYWORDS);
			
			string val = p.Value;
			foreach(string s in i_keywords)
			{
				if(val.IndexOf(s) == -1)
					continue;  // the keyword is not there
				else
				{
					val = val.Replace(s,""); // replace the string with nothing
					val = val.Replace(", ,",", "); // replace the commas if needed
				}
			}
			p.Value = val;  // save it
		}

		/// <summary>
		/// Returns a data table listing all known properties of the picture
		/// </summary>
		public DataTable PropertyTable
		{
			get
			{
				if(this.m_disposed)
				{
					throw new ObjectDisposedException("This instance of PictureProperties class was already disposed!");
				}

				DataTable PropertySet = new DataTable("PictureProperties");

				// Declare variables for DataColumn and DataRow objects.
				DataColumn myDataColumn;

				// Create new DataColumn, set DataType, ColumnName and add to DataTable.    
				myDataColumn = new DataColumn();
				myDataColumn.DataType = System.Type.GetType("System.String");
				myDataColumn.ColumnName = "Property";
				myDataColumn.AutoIncrement = false;
				myDataColumn.Caption = "Property";
				myDataColumn.ReadOnly = true;
				myDataColumn.Unique = false;
				// Add the column to the table.
				PropertySet.Columns.Add(myDataColumn);

				// Create second column.
				myDataColumn = new DataColumn();
				myDataColumn.DataType = System.Type.GetType("System.String");
				myDataColumn.ColumnName = "Value";
				myDataColumn.Caption = "Value";
				myDataColumn.ReadOnly = true;
				myDataColumn.Unique = false;
				// Add the Column to the DataColumnCollection.
				PropertySet.Columns.Add(myDataColumn);
	
				PropertySet.Clear();

				// Create three new DataRow objects and add them to the DataTable
				for(int a=0; a != m_Bitmap.PropertyItems.Length; a++)
				{
					DataRow myDataRow;
					ImageProperty p = new ImageProperty(m_Bitmap.PropertyItems[a]);

					if(!p.KnownProperty)
						continue;

					myDataRow = PropertySet.NewRow();
					myDataRow["Value"] = p.Value;
					myDataRow["Property"] = p.Name;
					PropertySet.Rows.Add(myDataRow);
				}

				return PropertySet;
			}
		}
	}
}

